﻿/*
 * Catalyst - http://catalystobjects.com
 * 
 * New BSD License (BSD)
 * 
 * Copyright (c) 2009, Ashley Brener
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of Midlet Earth nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Xml.Serialization;
using Catalyst.Caching;
using SubSonic;

namespace Catalyst
{
	//[AttributeUsage(AttributeTargets.Property)]
	//sealed class HiddenForDataBindingAttribute : Attribute
	//{
	//    private readonly bool _isHidden;
	//    public HiddenForDataBindingAttribute()
	//    {
	//    }

	//    public HiddenForDataBindingAttribute(bool isHidden)
	//    {
	//        _isHidden = isHidden;
	//    }

	//    public bool IsHidden
	//    {
	//        get { return _isHidden; }
	//    }
	//}

	[Serializable]
	public abstract class AbstractObjectList<ItemType, ListType> : RelationalObject, IList<ItemType>
		where ItemType : new()
		where ListType : AbstractObjectList<ItemType, ListType>, new()
	{
		public abstract override TableSchema.Table GetTableSchema();

		//public abstract override string GetProviderName();

		internal override void LoadInternal(IDataReader Reader)
		{
			Load(Reader);
		}

		internal bool isLoaded = false;
		[XmlIgnore]
		public override bool IsLoaded
		{
			get { return isLoaded; }
		}

		private void initialize()
		{
			if (items == null)
			{
				items = new List<ItemType>();
			}
		}

		#region IList<ItemType> Members

		public int IndexOf(ItemType item)
		{
			initialize();

			return items.IndexOf(item);
		}

		public void Insert(int index, ItemType item)
		{
			initialize();

			items.Insert(index, item);
		}

		public void RemoveAt(int index)
		{
			initialize();

			items.RemoveAt(index);
		}

		public ItemType this[int index]
		{
			get
			{
				initialize();
			
				return items[index];
			}
			set
			{
				initialize();

				items[index] = value;
			}
		}

		#endregion

		#region ICollection<ItemType> Members

		public void Add(ItemType item)
		{
			initialize();

			items.Add(item);
		}

		public void Clear()
		{
			initialize();

			items.Clear();
		}

		public bool Contains(ItemType item)
		{
			initialize();

			return items.Contains(item);
		}

		public void CopyTo(ItemType[] array, int arrayIndex)
		{
			initialize();

			items.CopyTo(array, arrayIndex);
		}

		public int Count
		{
			get
			{
				initialize();

				return items.Count;
			}
		}

		public bool IsReadOnly
		{
			get { return false; }
		}

		public bool Remove(ItemType item)
		{
			initialize();

			return items.Remove(item);
		}

		#endregion

		#region IEnumerable<ItemType> Members

		public IEnumerator<ItemType> GetEnumerator()
		{
			initialize();

			return items.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator()
		{
			initialize();

			return items.GetEnumerator();
		}

		#endregion

		//#region ITypedList Members

		//public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
		//{
		//    if ((listAccessors == null) || listAccessors.Length == 0)
		//    {
		//        return GetPropertyDescriptors(typeof(ItemType));
		//    }
		//    return null;
		//}

		//private static PropertyDescriptorCollection GetPropertyDescriptors(Type typeOfObject)
		//{
		//    PropertyDescriptorCollection typePropertiesCollection = TypeDescriptor.GetProperties(typeOfObject);
		//    ArrayList propertyDescriptorsToUse = new ArrayList();

		//    foreach (PropertyDescriptor property in typePropertiesCollection)
		//    {
		//        HiddenForDataBindingAttribute hiddenAttribute = (HiddenForDataBindingAttribute)property.Attributes[typeof(HiddenForDataBindingAttribute)];

		//        if (hiddenAttribute != null && hiddenAttribute.IsHidden)
		//        {
		//            continue;
		//        }
		//        propertyDescriptorsToUse.Add(property);
		//    }

		//    return new PropertyDescriptorCollection((PropertyDescriptor[])propertyDescriptorsToUse.ToArray(typeof(PropertyDescriptor)));
		//}

		//public string GetListName(PropertyDescriptor[] listAccessors)
		//{
		//    return String.Empty;
		//}

		//#endregion

		#region *** Load Data ***

		//internal abstract void Load<DataRecordType, DataRecordListType>(AbstractList<DataRecordType, DataRecordListType> DataRecordList)
		//    where DataRecordType : AbstractRecord<DataRecordType>, new()
		//    where DataRecordListType : AbstractList<DataRecordType, DataRecordListType>, new();

		private string getAllCacheKey(Type Type)
		{
			return String.Format("{0}_All{1}", Schema.Provider.Name, SubSonic.Utilities.Utility.SingularToPlural(Type.Name));
		}

		private List<T> getAllItems<T>() where T : ItemType, new()
		{
			SqlQuery query = new Select().From(Schema);

			if (Schema.Columns.Contains("name"))
			{
				query.OrderAsc("name");
			}

			ListType collection = new ListType();
			collection.Load<T>(query.ExecuteReader());

			return new List<T>(collection.Cast<T>());
		}

		private CacheSettings<List<T>> getAllCachedListItems<T>() where T : ItemType, new()
		{
			CacheSettings<List<T>> settings = new CacheSettings<List<T>>();
							
			SqlCacheDependency dependency = null;

			if (HttpContext.Current != null)
			{
				HashSet<string> cacheEnabledTables = new HashSet<string>(SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(Schema.Provider.DefaultConnectionString));
				foreach (string enabledTable in cacheEnabledTables)
				{
					if (enabledTable == Schema.TableName)
					{
						dependency = new SqlCacheDependency(Schema.Provider.Name, enabledTable);
						break;
					}
				}
			}

			settings.CachedObject = getAllItems<T>();
			settings.Dependency = dependency;

			return settings;
		}

		public ListType LoadAll<T>() where T : ItemType, new()
		{
			Schema = GetTableSchema();

			bool loadFromCache = false;

			if (HttpContext.Current != null)
			{
				HashSet<string> cacheEnabledTables = new HashSet<string>(SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(Schema.Provider.DefaultConnectionString));
				foreach (string enabledTable in cacheEnabledTables)
				{
					if (enabledTable == Schema.TableName)
					{
						loadFromCache = true;
						break;
					}
				}
			}

			List<ItemType> itemsToLoad = null;

			if (loadFromCache)
			{
				List<T> allItems = CachingService.GetCachedObject(getAllCacheKey(typeof(ItemType)), new CachingService.ObjectLoader<List<T>>(getAllCachedListItems<T>));
                
				itemsToLoad = Cloning.CloneHelper<List<ItemType>>.Clone(new List<ItemType>(allItems.Cast<ItemType>()));
				
			}
			else
			{
				itemsToLoad = new List<ItemType>(getAllItems<T>().Cast<ItemType>());
			}

			AddRange(itemsToLoad);

			return (ListType)this;
		}

		public virtual ListType LoadAll()
		{
			return LoadAll<ItemType>();
		}

		public void ClearCache()
		{
			//LoadSchema();

			Schema = GetTableSchema();

			HttpContext.Current.Cache.Remove(getAllCacheKey(typeof(ItemType)));			
		}

		public ListType ReloadAll()
		{
			return ReloadAll<ItemType>();
		}

		public ListType ReloadAll<T>() where T : ItemType, new()
		{
			ClearCache();

			return LoadAll<T>();
		}

		//public ListType Load(Query Query)
		//{
		//    Load(Query.ExecuteReader());

		//    return (ListType)this;
		//}

		public abstract ListType Load(TableSchema.Table Table, IDataReader Reader);

		public abstract ListType Load(IDataReader Reader);

		public abstract ListType Load<T>(IDataReader Reader) where T : ItemType, new();

		public abstract ListType Load(TableSchema.Table TableSchema, DataTable DataTable);

		public abstract ListType Load(DataTable TableSchema);

		#endregion

		internal List<ItemType> items;

		public void AddRange<T>(IEnumerable<T> collection) where T : ItemType
		{
			initialize();

			items.AddRange(collection.Cast<ItemType>());
		}

		public void AddRange(IEnumerable<ItemType> collection)
		{
			initialize();

			items.AddRange(collection);
		}

		public void Sort(IComparer<ItemType> comparer)
		{
			initialize();

			items.Sort(comparer);
		}
	}
}
